---
description:
  Introduce a new object type `Comment` (and the corresponding Prisma model) in your code and also
  write a `Link.comments` field for retrieving all the comments that belong to a `Link`.
---

# Graph Relations

So far you built a simple GraphQL API for creating and retrieving links.

Another popular feature of Hackernews is to comment on links for attacking and criticizing the
original poster. We definitely don't want to miss out on that comment feature!

In this chapter, you will introduce a new object type `Comment` (and the corresponding Prisma model)
in your code and also write a `Link.comments` field for retrieving all the comments that belong to a
`Link`.

## Adding the `Comment` Model

The first thing you need is a way to represent the comment data in the database. To do so, you can
add a `Comment` type to your Prisma data model.

You'll also want to add a _relation_ between the `Comment` and the existing `Link` type to express
that `Comments`s are _posted_ on `Links`s.

Open `prisma/schema.prisma` and add the following code, making sure to also update your existing
`Link` model accordingly:

```prisma filename="prisma/schema.prisma" {6,9-14}
model Link {
  id          Int      @id @default(autoincrement())
  createdAt   DateTime @default(now())
  description String
  url         String
  comments    Comment[]
}

model Comment {
  id        Int      @id @default(autoincrement())
  createdAt DateTime @default(now())
  body      String
  link      Link     @relation(fields: [linkId], references: [id])
  linkId    Int
}
```

Notice how you're adding a new _relation field_ called `comments` to the `Link` model that points to
a list of `Comment` instances. The `Comment` model then has a `link` field that point's to the
associated `Link` instance.

To hint this relation to Prisma, the `link` field on the `Comment` model must be annotated with
[the `@relation` attribute](https://prisma.io/docs/reference/tools-and-interfaces/prisma-schema/relations#the-relation-attribute).
This is a requirement for every relation field in your Prisma schema, and by doing so you define the
foreign keys of the affected table.

In this specific case, we have a
[one-to-many relationship](https://prisma.io/docs/concepts/components/prisma-schema/relations/one-to-many-relations).

A `Link` has many comments. A `Comment` belongs to one `Link`.

If this is quite new to you, don't worry! We're going to be adding a few of these relational fields,
and you'll get the hang of it as you go! For a deeper dive on relations with Prisma, check out these
[docs](https://prisma.io/docs/reference/tools-and-interfaces/prisma-schema/relations).

You might ask, Links might not have Comments, so why don't we annotate comments like this:

```Prisma
comments    Comment[]?
```

The reason is that Prisma does not support optional on lists. This is by design as when you execute
SQL queries, you always get rows back, not null. In case there are no results, you will get an empty
list of rows.

## Updating Prisma Client

This is a great time to refresh your memory on the workflow we described for your project at the end
of chapter 4!

After every change you make to the data model, you need to migrate your database and then
re-generate Prisma Client.

In the root directory of the project, run the following command:

```sh
npx prisma migrate dev --name "add-comment-model"
```

This command has now generated your second migration inside of `prisma/migrations`, and you can
start to see how this becomes a historical record of how your database evolves over time. This
script also runs the Prisma migration, so your new models and types are ready-to-use.

That might feel like a lot of steps, but the workflow will become automatic by the end of this
tutorial!

Your database is ready and Prisma Client is now updated to expose all the CRUD queries for the newly
added `Comment` model - woohoo! 🎉

## Extending the GraphQL Schema

Remember back when we were setting up your GraphQL server and discussed the process of schema-driven
development? It all starts with extending your schema definition with the new types and fields that
you want to add to the API.

In this case, you first want a way of creating a comment using a mutation field
(`Mutation.postCommentOnLink`) and also fetching a comment via its id using a query field
(`Query.comment`).

```graphql {4,9,18-21}
type Query {
  info: String!
  feed: [Link!]!
  comment(id: ID!): Comment
}

type Mutation {
  postLink(url: String!, description: String!): Link!
  postCommentOnLink(linkId: ID!, body: String!): Comment!
}

type Link {
  id: ID!
  description: String!
  url: String!
}

type Comment {
  id: ID!
  createdAt: String!
  body: String!
}
```

## Implementing the GraphQL Field Resolvers

### `Mutation.postCommentOnLink` Resolver

Let's start with the `Mutation.postCommentOnLink` resolver, so you can post comments on a link.

Within the `Mutation` object type resolver maps create a new `postCommentOnLink` field resolver
function.

```ts
const resolvers = {
  // ... other resolver maps ...
  Mutation: {
    // ... other field resolver functions
    async postCommentOnLink(
      parent: unknown,
      args: { linkId: string; body: string },
      context: GraphQLContext
    ) {}
  }
}
```

This is the skeleton of your new field resolver. As you might notice, it looks a lot familiar to the
`Mutation.postLink` field resolver function. Since it will write to the database, the implementation
will also look pretty familiar!

Add the following business logic within the resolver, that uses the newly generated Prisma
functions:

```ts filename="src/schema.ts"
const resolvers = {
  // ... other resolver maps ...
  Mutation: {
    // ... other field resolver functions
    async postCommentOnLink(
      parent: unknown,
      args: { linkId: string; body: string },
      context: GraphQLContext
    ) {
      const newComment = await context.prisma.comment.create({
        data: {
          linkId: parseInt(args.linkId),
          body: args.body
        }
      })

      return newComment
    }
  }
}
```

You pass two arguments for creating the new comment to the `comment.create` call.

1. **`linkId`** - this is the id of the link to the comment belongs to. GraphQL has a special scalar
   for describing ids that you used before, called `ID`. In general, it is the best practice to use
   it. The downside is that a GraphQL `ID` is always a string. Thus, within the resolver function,
   you must parse the actual integer value of the `ID`, as the SQLite database uses integer values
   for IDs.
2. **`body`** - this is the comment body, there is nothing special about it.

> The createdAt field will be automatically created by Prisma

You probably can't wait to try sending a mutation for creating your first comment.

So let's do it!

You already created some links before, so there should be a link with the id `1` within your
database. Use that id for creating a new comment that references the link with the id `1`.

Execute the following mutation operation:

```graphql
mutation postCommentOnLink {
  postCommentOnLink(linkId: "1", body: "This is my first comment!") {
    id
    createdAt
    body
  }
}
```

The response will look identical to this:

```json
{
  "data": {
    "postCommentOnLink": {
      "id": "1",
      "createdAt": "1733487844288",
      "body": "This is my first comment!"
    }
  }
}
```

Up next let's also try to create a comment with a `linkId` with no corresponding link in the
database.

Executing the following mutation operation:

```graphql
mutation postCommentOnLink {
  postCommentOnLink(linkId: "99999999999", body: "This is my second comment!") {
    id
    body
  }
}
```

Assuming that you did not already create 99999999999 link elements before, the response will now
look a bit different:

```json
{
  "errors": [
    {
      "message": "Unexpected error.",
      "locations": [
        {
          "line": 2,
          "column": 3
        }
      ],
      "path": ["postCommentOnLink"],
      "extensions": {
        "originalError": {
          "message": "\nInvalid `context.prisma.comment.create()` invocation in\nhackernews/src/schema.ts:69:52\n\n   66   args: { linkId: string; body: string },\n   67   context: GraphQLContext,\n   68 ) => {\n→  69   const comment = await context.prisma.comment.create(\n  Foreign key constraint failed on the field: `foreign key`",
          "stack": "Error: \nInvalid `context.prisma.comment.create()` invocation in\nhackernews/src/schema.ts:69:52\n\n   66   args: { linkId: string; body: string },\n   67   context: GraphQLContext,\n   68 ) => {\n→  69   const comment = await context.prisma.comment.create(\n  Foreign key constraint failed on the field: `foreign key`\n    at cb (hackernews/node_modules/@prisma/client/runtime/index.js:38703:17)\n    at PrismaClient._request (hackernews/node_modules/@prisma/client/runtime/index.js:40859:18)"
        }
      }
    }
  ],
  "data": null
}
```

Let's try to analyze this error:

```text
Invalid `context.prisma.comment.create()` invocation
Foreign key constraint failed on the field: `foreign key`
```

As you might notice, Prisma is not that great at giving us a clear description of what is happening.

But, since we know that the `linkId` field on the `Comment` model is a foreign key reference to the
`id` column on the `Link` model, we can conclude that the origin of this error is a missing `Link`
entity with the `linkId` `99999999999`.

Let's ignore this ugly error message for now. We will improve upon that within the next chapter.

Instead, you will first create a resolver for retrieving a comment via its id.

### `Query.comment` resolver

Within the `Query` object type resolver map create a new `comment` field resolver function.

```ts filename="src/schema.ts"
const resolvers = {
  // ... other resolver maps ...
  Query: {
    // ... other field resolver functions
    async comment(parent: unknown, args: { id: string }, context: GraphQLContext) {}
  }
  // ... other resolver maps ...
}
```

Within the resolver, you want to fetch the comment by its id.

Add the corresponding logic for retrieving a comment by its id.

```ts filename="src/schema.ts"
const resolvers = {
  // ... other resolver maps ...
  Query: {
    // ... other field resolver functions
    async comment(parent: unknown, args: { id: string }, context: GraphQLContext) {
      return context.prisma.comment.findUnique({
        where: { id: parseInt(args.id) }
      })
    }
  }
  // ... other resolver maps ...
}
```

Similar to our mutation resolver, we first need to parse the integer value of the `id` variable
value before using the `comment.findUnique` function for finding one single record with that
specific id.

Awesome, let's fetch our comment using a query operation!

Execute the following operation on GraphiQL:

```graphql
query comment {
  comment(id: 1) {
    id
    body
  }
}
```

Awesome! 🎉 Now you can fetch the newly created comment!

```json
{
  "data": {
    "comment": {
      "id": "1",
      "body": "This is my first comment!"
    }
  }
}
```

Let's also try to fetch a comment by an `id` that does not exist.

Execute the following operation on GraphiQL:

```graphql
query comment {
  comment(id: 999999999) {
    id
    body
  }
}
```

As expected, the comment does not exist, and we receive the following response:

```json
{
  "data": {
    "comment": null
  }
}
```

So now you implemented fetching a comment by its id.

Up next, let's utilize the graph part of GraphQL for connecting `Link` object type with the
`Comment` object type.

## `Link.comments` resolver

So far you only fetched single entities within your GraphQL operations.

Let's also add a one-to-many connection from the Link to Comment object types within the GraphQL
schema.

Add the `Link.comments` field to the GraphQL schema definition.

```graphql {5}
type Link {
  id: ID!
  description: String!
  url: String!
  comments: [Comment!]!
}
```

On the Prisma API level, the method call for loading will always return an array. It does not
distinguish between an empty array and an array with comments.

As we design our GraphQL schema we need to consider how we will model the concept of a Link without
comments.

We have a couple of options:

First in terms of the array itself, we can define it as nullable or not. Then for the elements of
the array, also there we can define them as nullable or not.

Here are the 4 options, first two are for nullable array and the last two for none nullable array:

1. `comments: [Comment]`
2. `comments: [Comment!]`
3. `comments: [Comment]!`
4. `comments: [Comment!]!`

Let's try to explain the differences between the options. We'll also describe the Typescript return
value of each option (which will be automatically generated in the later Codegen chapter).

Option 1, `[Comment]`, means that the elements both the array and its elements are nullable. The
Typescript return type of that resolver would be `Array<Comment | null> | null`. Representing
`there's no Comment related to the Link` in that option can be applied in many different ways - an
empty array, a null as array or an array with null elements. That means that the client who consumes
this will need to handle many cases.

Option 2, `[Comment!]`, means that the elements of the array are none nullable, but the array can be
empty or null. The Typescript return type of that resolver would be `Array<Comment> | null`.

Representing `there's no Comment related to the Link` in that option can be applied in many
different ways - the array can be null or be empty, but at least the array can't have null elements.
Here also the client needs to handle a couple of different possibilities.

Option 3, `[Comment]!`, means that the array is none nullable, but the elements can be null.

Representing `there's no Comment related to the Link` in that option can be applied in many
different ways - it can't be a null as array, but it can be an empty array or an array with null
elements. Here also the client needs to handle a couple of the different possibilities.

Option 4, `[Comment!]!`, means that the elements of the array are none nullable and the array itself
must return a value. The Typescript return type for of resolver would be `Array<Comment>`.

With `[Comment!]!`, the only way for the server to represent
`there's no Comment related to the Link` is to return an empty array `[]`.

This means it's easier for the client to handle because there's **only one way** to represent the
`there's no Comment related to the Link` concept.

For the reasons above we've chose option `4` for our implementation.

Now let's implement the corresponding `Link.comments` resolver. For this, you need to touch the
`Link` object types resolver map.

As we also have the createdAt field on comments, let's sort the comments by that field when we
return them to the user.

Add the following `Link.resolvers` implementation:

```ts filename="src/schema.ts"
const resolvers = {
  // ... other resolver maps ...
  Link: {
    // ... other field resolver functions
    comments: (parent: Link, args: {}, context: GraphQLContext) => {
      return context.prisma.comment.findMany({
        orderBy: { createdAt: 'desc' },
        where: {
          linkId: parent.id
        }
      })
    }
  }
  // ... other resolver maps ...
}
```

As explained before, the first resolver function argument is always the parent argument. In the case
of the context of a `Link` object type resolver, it is the type of the `Link` model as exported from
the `@prisma/client` package.

Within the resolver logic, we fetch all the comments that belong to the parent `Link` element by
adding a where filter on the `linkId` for the value `parent.id`.

Let's see that in action!

Execute the following operation on GraphiQL:

```graphql
query feed {
  feed {
    id
    comments {
      id
      body
    }
  }
}
```

Congratulations! You just fetched the feed of Link elements and the comments that belong to those
Link elements within a single query operation!

```json
{
  "data": {
    "feed": [
      {
        "id": "1",
        "comments": [
          {
            "id": "1",
            "body": "This is my first comment!"
          }
        ]
      }
    ]
  }
}
```

Being able to fetch multiple resources within a single request is one of the main advantages of
using GraphQL!

### Optional Exercise

As an additional exercise, you can gain more hands-on experience by implementing the resolvers for
`Comment.link` and `Query.link`.

```graphql
type Comment {
  link: Link!
}

type Query {
  link(id: ID!): Link
}
```

Start by adding the fields to the schema definitions and then implement the actual field resolvers.

If you are struggling check out the implementations of the previous resolvers! There is nothing new
happening here. You can do it!
